
#include <iostream>
#include <string>
#include <vector>
#include "mysql_connection.h"

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

extern "C" {
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <stdio.h>
#include <string.h>


    static char* ngx_http_multipost(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
    static ngx_int_t ngx_http_multipost_handler(ngx_http_request_t* r);
    static ngx_int_t multipost_subrequest_post_handler(ngx_http_request_t* r, void* data, ngx_int_t rc);
    static ngx_int_t multipost_subrequest_post_handler2(ngx_http_request_t* r, void* data, ngx_int_t rc);
    static ngx_int_t multipost_subrequest_post_handler3(ngx_http_request_t* r, void* data, ngx_int_t rc);
    static void multipost_post_handler(ngx_http_request_t* r);
    static ngx_int_t multipost_process_subrequest_post_handler(ngx_http_request_t* r, void* data, ngx_int_t rc, int index);
    static u_char* get_sessionid(ngx_http_request_t* r);
    static void print_header(ngx_http_request_t* r);
    static void copy_header(ngx_http_request_t* r, ngx_http_request_t* sr);
    static u_char* get_out_sessionid(ngx_http_request_t* r);
    static u_char* get_sub_req_out_sessionid(ngx_http_request_t* r);
    static u_char* get_par_req_out_sessionid(ngx_http_request_t* r);
}

sql::ResultSet* session_query(std::string sql);
int session_update(std::string sql);
std::string rand32();
static ngx_int_t body_diff(ngx_str_t body1, ngx_str_t body2);

#define FIRST_SERVER "/first"
#define SECOND_SERVER "/second"
#define THIRD_SERVER "/third"
#define SERVER_NUM 3
#define RANDOM_SIZE  32


typedef struct {
    ngx_str_t stock_info[SERVER_NUM];
    int finished_subrequest_num;
    ngx_str_t content_type;
    ngx_http_request_t* sr[SERVER_NUM];
} ngx_http_multipost_ctx_t;


//处理配置项
static ngx_command_t ngx_http_multipost_commands[] = {
    {
        ngx_string("multipost"),
        NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
        ngx_http_multipost,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_multipost_module_ctx = {
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

ngx_module_t ngx_http_multipost_module = {
    NGX_MODULE_V1,
    &ngx_http_multipost_module_ctx,
    ngx_http_multipost_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};



//配置项对应的回调函数
static char* ngx_http_multipost(ngx_conf_t* cf, ngx_command_t* cmd, void* conf) {
    ngx_http_core_loc_conf_t* clcf = (ngx_http_core_loc_conf_t*)ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_multipost_handler;
    return NGX_CONF_OK;
}

static ngx_int_t ngx_create_subrequest(ngx_http_request_t* r, ngx_http_multipost_ctx_t* myctx,
                                       ngx_http_post_subrequest_pt handler, std::string server, ngx_str_t* jsessionid) {

    //以下创建子请求
    ngx_http_post_subrequest_t* psr = (ngx_http_post_subrequest_t*)ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if(psr == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    //绑定子请求handler
    psr->handler = handler;
    psr->data = myctx;

    ngx_str_t sub_prefix;
    sub_prefix.len = server.length();
    sub_prefix.data = (u_char*)server.c_str();

    std::cout << sub_prefix.data << std::endl;

    ngx_str_t sub_location;
    sub_location.len = server.length() + r->uri.len;
    sub_location.data = (u_char*) ngx_palloc(r->pool, sub_location.len);
    ngx_snprintf(sub_location.data, sub_location.len, "%V%V", &sub_prefix, &r->uri);

    //第三个参数为请求参数，为空表示不传参
    ngx_http_request_t* sr = (ngx_http_request_t*)ngx_palloc(r->pool, sizeof(ngx_http_request_t));
    ngx_int_t rc = ngx_http_subrequest(r, &sub_location, &r->args, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY);
    if(rc != NGX_OK)
        return NGX_ERROR;

    //深复制headers_in
    copy_header(r, sr);

    //获取子请求的sessionid,并复制
    if(jsessionid->len > 0) {
        u_char* sjsessionid = get_sessionid(sr);
        if(sjsessionid != NULL)
            strncpy((char*)sjsessionid, (char*)jsessionid->data, 32);
        //std::cout << sjsessionid << std::endl;
        //std::cout << std::endl;
    }

    print_header(r);
    print_header(sr);

    // ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "sr地址是：%s. ", *sr);
    // ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "r地址是：%Xp. ", *r);

    return rc;
}


//接收客户请求时的方法
static ngx_int_t ngx_http_multipost_handler(ngx_http_request_t* r) {

    //获取模块存储在ngx_http_request_t里的ctx数据
    ngx_http_multipost_ctx_t* myctx = (ngx_http_multipost_ctx_t*)ngx_http_get_module_ctx(r, ngx_http_multipost_module);
    if(myctx == NULL) {
        myctx = (ngx_http_multipost_ctx_t*)ngx_palloc(r->pool, sizeof(ngx_http_multipost_ctx_t));
        if(myctx == NULL)
            return NGX_ERROR;
        ngx_http_set_ctx(r, myctx, ngx_http_multipost_module);
    }

    //已完成子请求数设为0
    myctx->finished_subrequest_num = 0;

    u_char* jsessionid = get_sessionid(r);
    sql::ResultSet* res;
    std::vector<std::string> session(SERVER_NUM + 1);
    if(jsessionid) {

        std::string sjsessionid((const char*)jsessionid, 32);
        std::string sql = "SELECT * from session where jsessionid='" + sjsessionid + "'";

        res = session_query(sql);
        if(res) {
            while(res->next()) {
                std::cout << res->getString("jsessionid") << std::endl;
                session[1] = res->getString("session1");
                session[2] = res->getString("session2");
                session[3] = res->getString("session3");
            }
        } else {
            //没有sessionid
            session[1] = "";
            session[2] = "";
            session[3] = "";
        }
    }

    ngx_str_t sessions[4];
    sessions[1].len = session[1].size();
    sessions[1].data = (u_char*)session[1].c_str();
    sessions[2].len = session[2].size();
    sessions[2].data = (u_char*)session[2].c_str();
    sessions[3].len = session[3].size();
    sessions[3].data = (u_char*)session[3].c_str();

    ngx_int_t rc;
    rc = ngx_create_subrequest(r, myctx, multipost_subrequest_post_handler, FIRST_SERVER, &sessions[1]);
    if(rc != NGX_OK)
        return NGX_ERROR;

    rc = ngx_create_subrequest(r, myctx, multipost_subrequest_post_handler2, SECOND_SERVER, &sessions[2]);
    if(rc != NGX_OK)
        return NGX_ERROR;

    rc = ngx_create_subrequest(r, myctx, multipost_subrequest_post_handler3, THIRD_SERVER, &sessions[3]);
    if(rc != NGX_OK)
        return NGX_ERROR;

    return NGX_DONE;

}



//子请求结束后的回调方法
static ngx_int_t multipost_process_subrequest_post_handler(ngx_http_request_t* r, void* data, ngx_int_t rc, int index) {

    ngx_http_request_t* pr = r->parent;
    ngx_http_multipost_ctx_t* myctx = (ngx_http_multipost_ctx_t*)ngx_http_get_module_ctx(pr, ngx_http_multipost_module);

    //如果已完成请求是SERVER_NUM，代表请求已经完成了
    if(myctx->finished_subrequest_num >= SERVER_NUM)
        return NGX_OK;

    myctx->sr[index] = r;
    //父请求返回状态设置为子请求的返回状态
    pr->headers_out.status = r->headers_out.status;

    //缓冲区数据赋值到myctx->stock_info中
    ngx_buf_t* pRecvBuf = &(r->upstream->buffer);
    myctx->stock_info[index].data = pRecvBuf->pos;
    myctx->stock_info[index].len = pRecvBuf->last - pRecvBuf->pos;

    //如果子请求返回状态是成功
    if(r->headers_out.status == NGX_HTTP_OK)
        myctx->content_type = r->headers_out.content_type;

    //已完成子请求数+1，注意，这里是异步的，可能会产生线程同步问题
    myctx->finished_subrequest_num++;
    //子请求结束后，调用父请求回调方法
    pr->write_event_handler = multipost_post_handler;
    return NGX_OK;
}


//第一个子请求结束时的回调方法
static ngx_int_t multipost_subrequest_post_handler(ngx_http_request_t* r, void* data, ngx_int_t rc) {
    return multipost_process_subrequest_post_handler(r, data, rc, 0);
}

//第二个子请求结束时的回调方法
static ngx_int_t multipost_subrequest_post_handler2(ngx_http_request_t* r, void* data, ngx_int_t rc) {
    return multipost_process_subrequest_post_handler(r, data, rc, 1);
}

//第三个子请求结束时的回调方法
static ngx_int_t multipost_subrequest_post_handler3(ngx_http_request_t* r, void* data, ngx_int_t rc) {
    return multipost_process_subrequest_post_handler(r, data, rc, 2);
}

//父请求回调方法
static void multipost_post_handler(ngx_http_request_t* r) {

    ngx_http_multipost_ctx_t* myctx = (ngx_http_multipost_ctx_t*)ngx_http_get_module_ctx(r, ngx_http_multipost_module);
    if(myctx->finished_subrequest_num != SERVER_NUM) {
        //两个请求没有全部完成
        return;
    }

    //保存返回的包体信息
    FILE* fp[SERVER_NUM];
    fp[0] = fopen("/tmp/content_from_first.tmp", "w+");
    fp[1] = fopen("/tmp/content_from_second.tmp", "w+");
    fp[2] = fopen("/tmp/content_from_third.tmp", "w+");
    for(int i = 0; i < SERVER_NUM; i++) {
        fwrite(myctx->stock_info[i].data, myctx->stock_info[i].len, 1, fp[i]);
        fclose(fp[i]);
    }


    std::string out_session[SERVER_NUM + 1];
    for(int i = 0; i < SERVER_NUM; i++) {
        out_session[i + 1] = std::string((char*)(get_sub_req_out_sessionid(myctx->sr[i])), 32);
        std::cout << "返回的虚拟session " << out_session[i + 1] << std::endl;
        std::cout << std::endl;
    }

    std::string ret_session;
    std::string query_sql = "SELECT * FROM session WHERE session1='" + out_session[1] + "' AND session2='" + out_session[2] + "' AND session3='" + out_session[3] + "'";
    sql::ResultSet* query_res = session_query(query_sql);
    if(query_res->rowsCount() > 0) {
        //如果能找到，说明客户端也保存了sessionid，直接返回即可
        // while(res->next())
        //     ret_session == res->getString("jsessionid");

        // //找到父请求的sessionid位置
        // u_char* res_sess = get_par_req_out_sessionid(r);
        // printf("修改之前 %*s\n", 32, (char*)res_sess);

        // strncpy((char*)res_sess, ret_session.c_str(), 32);

    } else {
        std::cout << "这行的else" << std::endl;
        //如果找不到，就新建
        ret_session = rand32();
        std::string update_sql = "INSERT INTO session VALUES ('" + ret_session + "','" + out_session[1] + "','" + out_session[2] + "','" + out_session[3] + "')";
        std::cout << update_sql << std::endl;
        int res = session_update(update_sql);

        if(res > 0) {
            std::cout << "数据库添加成功" << std::endl;
            //如果需要新建session，说明客户端没有保存session
            ngx_table_elt_t* h = (ngx_table_elt_t*)ngx_list_push(&r->headers_out.headers);
            if(h == NULL)
                return;
            h->hash = 1;
            h->key.len = sizeof("Set-Cookie") - 1;
            h->key.data = (u_char*)"Set-Cookie";

            std::string session_value = "JSESSIONID=" + ret_session + "; Path=/; HttpOnly";

            ngx_str_t prefix = ngx_string("JSESSIONID=");
            ngx_str_t post = ngx_string("; Path=/; HttpOnly");

            h->value.len = session_value.size();
            h->value.data = (u_char*) ngx_palloc(r->pool, h->value.len);
            ngx_snprintf(h->value.data, h->value.len, "%V%*s%V", &prefix, 32, (char*)ret_session.c_str(), &post);

            u_char* ssess = get_par_req_out_sessionid(r);
            printf("添加之后 %s\n", (char*)ssess);
        } else
            std::cout << "数据库添加失败" << std::endl;
    }

    //设定返回数据
    // ngx_str_t lastResultStr = myctx->stock_info[0];
    // std::cout << "first package data  ";
    // std::cout << myctx->stock_info[0].data << std::endl;
    // std::cout << "second package data  ";
    // std::cout << myctx->stock_info[1].data << std::endl;

    ngx_str_t lastResultStr;
    //择多处理
    ngx_int_t diff_01 = body_diff(myctx->stock_info[0], myctx->stock_info[1]);
    if(diff_01 == 0)    //说明至少两个包结果一样，直接返回这个包
        lastResultStr = myctx->stock_info[0];
    else {
        ngx_int_t diff_02 = body_diff(myctx->stock_info[0], myctx->stock_info[2]);
        if(diff_02 == 0)
            lastResultStr = myctx->stock_info[0];
        else
            lastResultStr = myctx->stock_info[2];
    }

    int bodylen = lastResultStr.len;
    r->headers_out.content_length_n = bodylen;
    //分配内存池
    ngx_buf_t* b = ngx_create_temp_buf(r->pool, bodylen);
    b->pos = lastResultStr.data;
    b->last = b->pos + bodylen;
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;


    r->headers_out.content_type = myctx->content_type;
    r->headers_out.status = NGX_HTTP_OK;
    r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED;
    ngx_int_t ret = ngx_http_send_header(r);
    ret = ngx_http_output_filter(r, &out);


    ngx_http_finalize_request(r, ret);
}

static u_char* get_sessionid(ngx_http_request_t* r) {
    //搜索请求头
    ngx_list_part_t* part = &r->headers_in.headers.part;
    ngx_table_elt_t* header = (ngx_table_elt_t*)part->elts;

    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "header的地址是：%Xp. ", header);

    for(unsigned int i = 0; /* void */; i++) {
        if(i >= part->nelts) {
            if(part->next == NULL)
                break;
            part = part->next;
            header = (ngx_table_elt_t*)part->elts;
            i = 0;
        }

        if(header[i].hash == 0)
            continue;

        if(ngx_strncasecmp(header[i].key.data, (u_char*)"Cookie", header[i].key.len) == 0) {
            //printf("get_sessionid = %*s : %*s \n", (int)header[i].key.len, header[i].key.data, (int)header[i].value.len, header[i].value.data);
            if(ngx_strncasecmp(header[i].value.data, (u_char*)"JSESSIONID", sizeof("JSESSIONID") - 1) == 0)
                return header[i].value.data + sizeof("JSESSIONID");
        }
    }
    return NULL;
}



static u_char* get_out_sessionid(ngx_http_request_t* r) {
    //搜索请求头
    ngx_list_part_t* part = &r->headers_out.headers.part;
    ngx_table_elt_t* header = (ngx_table_elt_t*)part->elts;

    for(unsigned int i = 0; /* void */; i++) {
        if(i >= part->nelts) {
            if(part->next == NULL)
                break;
            part = part->next;
            header = (ngx_table_elt_t*)part->elts;
            i = 0;
        }

        if(header[i].hash == 0)
            continue;


        if(ngx_strncasecmp(header[i].key.data, (u_char*)"Set-Cookie", header[i].key.len) == 0) {
            printf("%*s : %*s \n", (int)header[i].key.len, header[i].key.data, (int)header[i].value.len, header[i].value.data);
            if(ngx_strncasecmp(header[i].value.data, (u_char*)"JSESSIONID", sizeof("JSESSIONID") - 1) == 0)
                return header[i].value.data + sizeof("JSESSIONID");
        }
    }
    return NULL;
}

static u_char* get_sub_req_out_sessionid(ngx_http_request_t* r) {
    u_char* temp = get_out_sessionid(r);
    return temp ? temp : get_sessionid(r);
}

static u_char* get_par_req_out_sessionid(ngx_http_request_t* r) {
    u_char* temp = get_out_sessionid(r);
    return temp ? temp : NULL;
}

sql::ResultSet* session_query(std::string sql) {
    try {
        sql::Driver* driver = get_driver_instance();
        sql::Connection* con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
        con->setSchema("multipost");
        sql::Statement* stmt = con->createStatement();
        sql::ResultSet* res = stmt->executeQuery(sql);
        delete stmt;
        return res;

    } catch(sql::SQLException& e) {
        return NULL;
    }
}

int session_update(std::string sql) {
    try {
        sql::Driver* driver = get_driver_instance();
        sql::Connection* con = driver->connect("tcp://127.0.0.1:3306", "root", "root");
        con->setSchema("multipost");
        sql::Statement* stmt = con->createStatement();
        int res = stmt->executeUpdate(sql);
        delete stmt;
        return res;
    } catch(sql::SQLException& e) {
        return -1;
    }
}

std::string rand32() {
    char CCH[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    srand((unsigned)time(NULL));
    char ch[RANDOM_SIZE + 1] = {0};
    for(int i = 0; i < RANDOM_SIZE; ++i)
        ch[i] = CCH[rand() % (sizeof(CCH) - 1)];
    return std::string(ch, 32);
}

static ngx_int_t body_diff(ngx_str_t body1, ngx_str_t body2) {
    size_t i;
    ngx_int_t ret = 0;
    if(body1.len != body2.len)
        return -1;
    for(i = 0; i < body1.len; i++) {
        if(*(body1.data + i) != *(body2.data + i))
            ret++;
    }
    return ret;
}


static void print_header(ngx_http_request_t* r) {
    //搜索请求头
    ngx_list_part_t* part = &r->headers_in.headers.part;
    ngx_table_elt_t* header = (ngx_table_elt_t*)part->elts;

    //ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "header的地址是：%Xp. ", header);

    for(unsigned int i = 0; /* void */; i++) {
        if(i >= part->nelts) {
            if(part->next == NULL)
                break;
            part = part->next;
            header = (ngx_table_elt_t*)part->elts;
            i = 0;
        }

        if(header[i].hash == 0)
            continue;

        //printf("%*s : %*s \n", (int)header[i].key.len, header[i].key.data,
        //       (int)header[i].value.len, header[i].value.data);
    }
}


static void copy_header(ngx_http_request_t* r, ngx_http_request_t* sr) {
    ngx_list_t* src_list = &r->headers_in.headers;
    ngx_list_t* dst_list = ngx_list_create(sr->pool, src_list->nalloc, src_list->size);

    //搜索请求头
    ngx_list_part_t* part = &src_list->part;
    ngx_table_elt_t* header = (ngx_table_elt_t*)part->elts;

    for(unsigned int i = 0; /* void */; i++) {
        if(i >= part->nelts) {
            if(part->next == NULL)
                break;
            part = part->next;
            header = (ngx_table_elt_t*)part->elts;
            i = 0;
        }

        if(header[i].hash == 0)
            continue;

        ngx_table_elt_t* cpy = (ngx_table_elt_t*)ngx_list_push(dst_list);
        if(cpy == NULL)
            return;

        cpy->hash = header[i].hash;

        cpy->key.len = header[i].key.len;
        cpy->key.data = (u_char*)ngx_palloc(sr->pool, cpy->key.len);
        ngx_snprintf(cpy->key.data, cpy->key.len, "%*s", header[i].key.len, header[i].key.data);

        cpy->value.len = header[i].value.len;
        cpy->value.data = (u_char*)ngx_palloc(sr->pool, cpy->value.len);
        ngx_snprintf(cpy->value.data, cpy->value.len, "%*s", header[i].value.len, header[i].value.data);

        cpy->lowcase_key = (u_char*)ngx_palloc(sr->pool, header[i].key.len);
        ngx_strlow(cpy->lowcase_key, cpy->key.data, cpy->key.len);

        //printf("%*s : %*s \n", (int)header[i].key.len, header[i].key.data, (int)header[i].value.len, header[i].value.data);
        //printf("%*s : %*s \n", (int)cpy->key.len, cpy->key.data, (int)cpy->value.len, cpy->value.data);
    }
    sr->headers_in.headers = *dst_list;
}
